package il.ac.mta.javaweb.ex1.ui;

import il.ac.mta.javaweb.ex1.logic.BattleShip;
import il.ac.mta.javaweb.ex1.logic.BattleShipGameLoader;
import il.ac.mta.javaweb.ex1.logic.BattleShipsGame;
import il.ac.mta.javaweb.ex1.logic.ShipDirection;
import il.ac.mta.javaweb.ex1.logic.grids.BattleShipsGrid;
import il.ac.mta.javaweb.ex1.logic.grids.MarkingGrid;
import il.ac.mta.javaweb.ex1.logic.players.ComputerPlayer;
import il.ac.mta.javaweb.ex1.logic.players.HumanPlayer;
import il.ac.mta.javaweb.ex1.logic.players.PlayerBase;

import java.awt.Point;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

public class UIManager
{
	private static final String START_ANOTHER_GAME_WITH_SAME_PREFS = "0";
	private static final char ZERO_DIGIT_CHAR = '0';
	private static final String START_MANUAL_GAME = "1";
	private static final String START_PRE_SET_GAME = "2";
	private static final String QUIT = "Q";
	private static final String BACK = "B";
	private static boolean userWantsToQuit = false;
	private static boolean currentPlayerHitShip = false;

	private static BattleShipsGame game = new BattleShipsGame();
	private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

	public static void run()
	{
		try
		{
			runMainMenu();
		}
		catch (IOException e)
		{
			System.out.println("A IO exception has happened. Please restart the game.");
		}
	}

	private static void runMainMenu() throws IOException
	{
		boolean keepRunning = true;
		boolean firstRun = true;
		String input = "";

		// Main loop of the game
		while (keepRunning && !userWantsToQuit)
		{
			try
			{
				if (firstRun)
				{
					printMainMenuForFirstRun();

					if (input.equals(START_MANUAL_GAME))
					{
						initManualGame();
						startGame();
						firstRun = false;
					}
					else if (input.equals(START_PRE_SET_GAME))
					{
						try
						{
							initGameFromXml();
							startGame();
							firstRun = false;
						}
						catch (ParserConfigurationException e)
						{
							System.out.println("The file is not in the expected format.");
							game = new BattleShipsGame();
						}
						catch (IOException e)
						{
							System.out.println("The file could not be found.");
							game = new BattleShipsGame();
						}
						catch (IllegalArgumentException e)
						{
							System.out.println(e.getMessage());
							game = new BattleShipsGame();
						}
						catch (Exception e)
						{
							System.out
									.println("The file could not be found or is not in the correct format.");
							game = new BattleShipsGame();
						}
						finally
						{
							
						}
					}
					else if (input.equals(QUIT))
					{
						userWantsToQuit = true;
						return;
					}
					else
					{
						System.out.println("The entered input is illegal. Valid input is 1-3|Q\n");
						continue;
					}
				}
				// any run after first successful run
				else
				{
					printMainMenuForSecondAndLaterRuns();
					input = reader.readLine().trim().toUpperCase();

					if (input.equals(START_ANOTHER_GAME_WITH_SAME_PREFS))
					{
						game.Init(game.getPlayers());
						startGame();
					}
					else
					{
						if (input.equals(START_MANUAL_GAME))
						{
							initManualGame();
							startGame();
						}
						else if (input.equals(START_PRE_SET_GAME))
						{
							initGameFromXml();
							startGame();
						}
						else if (input.equals(QUIT))
						{
							userWantsToQuit = true;
							return;
						}
						else
						{
							System.out
									.println("The entered input is illegal. Valid input is 1-3|Q\n");
							continue;
						}
					}
				}
			}
			catch (Exception e)
			{
				System.out.println("An unexpected error has occured");
			}
		}
	}

	private static void printMainMenuForSecondAndLaterRuns()
	{
		System.out.println("Game ended... What do you want to do now?");
		System.out.println();
		System.out.println("0. Start another game with the same preferences.");
		System.out.println("1. Create a manual board.");
		System.out.println("2. Use a pre set board.");
		System.out.println("Q. Exit...");
		System.out.println();
		System.out.println("Please enter your choice.");
	}

	private static void startGame() throws IOException
	{
		boolean gameEnded = false;
		Hashtable<String, Boolean> markForDeletion = new Hashtable<String, Boolean>();

		initDeletionMarking(markForDeletion);

		// Main game loop
		while (!gameEnded)
		{
			// Iterate over all players
			for (PlayerBase player : game.getPlayers())
			{
				if (markForDeletion.get(player.getName()) == true)
				{
					continue;
				}

				if (player.getOwnGrid().allShipsSunk())
				{
					markForDeletion.put(player.getName(), true);
					continue;
				}

				boolean turnEnded = false;
				while (!turnEnded && !gameEnded)
				{
					if (player.getClass() == ComputerPlayer.class)
					{
						PlayerBase attackedPlayer = handleComputerPlayerTurn(player);

						checkIfAttackedPlayerLost(markForDeletion, attackedPlayer);

						turnEnded = true && !currentPlayerHitShip;
					}
					else
					{
						turnEnded = handleHumanPlayerTurn(player, markForDeletion)
								&& !currentPlayerHitShip;
					}

					currentPlayerHitShip = false;
					gameEnded = checkIfGameHasEnded(markForDeletion, player);
				}
			}
		}
	}


	private static void checkIfAttackedPlayerLost(Dictionary<String, Boolean> markForDeletion,
			PlayerBase attackedPlayer)
	{
		if (attackedPlayer.getOwnGrid().allShipsSunk())
		{
			System.out.println(String.format("All the ships of %1$s have sunk. %1$s has lost",
					attackedPlayer.getName()));
			markForDeletion.put(attackedPlayer.getName(), true);
		}
	}

	private static boolean checkIfGameHasEnded(Hashtable<String, Boolean> markForDeletion,
			PlayerBase currentPlayer)
	{
		int countLosers = 0;
		
		for (String player : markForDeletion.keySet())
		{
			if (markForDeletion.get(player))
			{
				++countLosers;
			}
		}
		
		if (countLosers + 1 == game.getPlayers().size())
		{
			System.out.println(String.format("\n *** %s has won the game! ***\n\n",
					currentPlayer.getName()));

			return true;
		}

		return false;
	}

	private static PlayerBase handleComputerPlayerTurn(PlayerBase player)
	{
		ComputerPlayer pcPlayer = (ComputerPlayer) player;
		MarkingGrid attackedPlayerMarkingGrid = pcPlayer.playTurn();

		System.out.println(String.format("%s attacked %s at position %s", pcPlayer.getName(),
				attackedPlayerMarkingGrid.getPlayer().getName(),
				BattleShipGameLoader.mapBoatPositionToString(pcPlayer.getLastAttackedPosition())));

		System.out.println(attackedPlayerMarkingGrid);

		if (attackedPlayerMarkingGrid.getPlayer().getOwnGrid()
				.isHitAtPoint(pcPlayer.getLastAttackedPosition()))
		{
			currentPlayerHitShip = true;
		}

		return attackedPlayerMarkingGrid.getPlayer();
	}


	private static boolean handleHumanPlayerTurn(PlayerBase player,
			Dictionary<String, Boolean> markForDeletion)
			throws IOException
	{
		String input;
		System.out.println(String.format("\n\n%1s\'s turn", player.getName()));
		System.out.println("Please choose the number of a grid to act on. or Q to leave the game");
		System.out.println("Your options are:\n\n");

		int index = printMarkingGridsOfPlayer(player);

		input = reader.readLine().trim().toUpperCase();

		Integer selection = null;

		if (input.equals(QUIT))
		{
			executePlayerLeavingTheGame(player, markForDeletion);
			return true;
		}

		try
		{
			selection = Integer.parseInt(input);
		}
		catch (NumberFormatException e)
		{
			System.out.println("The input is not valid");
			return false;
		}

		if (selection > 0 && selection <= index)
		{
			MarkingGrid gridToAttack = player.getEnemiesMarkingGrids().get(
					selection - 1);

			printAvailableActions(gridToAttack);

			input = reader.readLine().trim().toUpperCase();

			if (!input.equals("1") && !input.equals("2") && !input.equals("3")
					&& !input.equals(QUIT))
			{
				System.out.println("The input is invalid");
			}
			else if (input.equals(QUIT))
			{
				executePlayerLeavingTheGame(player, markForDeletion);
				return true;
			}
			else
			{
				System.out.println("Please choose the required position (A1 - J10)");

				Point position = BattleShipGameLoader.mapBoatPositionToPoint(reader
						.readLine().trim().toUpperCase());

				if (position != null)
				{
					return performAction(player, input, gridToAttack, position, markForDeletion);
				}
				else
				{
					System.out.println("The input is not valid");
				}
			}
		}

		return false;
	}

	private static void executePlayerLeavingTheGame(PlayerBase player,
			Dictionary<String, Boolean> markForDeletion)
	{
		System.out.println(String.format("%s ha left the game\n\n", player.getName()));
		markForDeletion.put(player.getName(), true);
	}


	private static void initDeletionMarking(Dictionary<String, Boolean> markForDeletion)
	{
		for (PlayerBase player : game.getPlayers())
		{
			markForDeletion.put(player.getName(), false);
		}
	}

	private static boolean performAction(PlayerBase attacker, String input,
			MarkingGrid gridToAttack, Point position, Dictionary<String, Boolean> markForDeletion)
	{
		if (input.equals("1"))
		{
			gridToAttack.mark(position);
		}
		else if (input.equals("2"))
		{
			gridToAttack.unmark(position);
		}
		else if (input.equals("3"))
		{
			gridToAttack.getPlayer().getOwnGrid().bomb(position);
			gridToAttack.markAsBombed(position);

			System.out.println(String.format("%1s attacked %2s at position %3s",
					attacker.getName(), gridToAttack.getPlayer().getName(),
					BattleShipGameLoader.mapBoatPositionToString(position)));

			System.out.println(gridToAttack);

			checkIfAttackedPlayerLost(markForDeletion, gridToAttack.getPlayer());
			
			if (gridToAttack.getPlayer().getOwnGrid().isHitAtPoint(position))
			{
				currentPlayerHitShip = true;
			}

			return true;
		}

		return false;
	}

	private static void printAvailableActions(MarkingGrid gridToAttack)
	{
		System.out.println(String.format("You chose to act on %1s\'s grid:",
				gridToAttack.getPlayer()));
		System.out.println(gridToAttack + "\n");

		System.out.println("Please choose your action:");
		System.out.println("1. Mark a square");
		System.out.println("2. Unmark a square");
		System.out.println("3. Bomb a square");
		System.out.println("Q. Leave game");
	}

	private static int printMarkingGridsOfPlayer(PlayerBase player)
	{
		int index = 0;
		for (MarkingGrid grid : player.getEnemiesMarkingGrids())
		{
			System.out.println(String.format("%1d. %2s\'s grid", ++index,
					grid.getPlayer()));
			System.out.println("---------------------\n");
			System.out.println(grid);
		}
		return index;
	}

	private static void printMainMenuForFirstRun()
	{
		System.out.println("Welcome To BattleShips...");
		System.out.println();
		System.out.println("1. Create a manual board.");
		System.out.println("2. Use a pre set board.");
		System.out.println("Q. Exit...");
		System.out.println();
		System.out.println("Please enter your choice.");
	}

	private static void initManualGame() throws IOException
	{
		boolean returnToMainMenu = false;
		List<PlayerBase> players = new ArrayList<PlayerBase>();

		int numOfPlayers = runSetNumOfPlayersMenu();

		for (int i = 0; i < numOfPlayers; ++i)
		{
			PlayerBase player = runCreateNewPlayerMenu();

			if (player == null)
			{
				returnToMainMenu = true;
				break;
			}

			players.add(player);
		}

		if (returnToMainMenu)
		{
			game.reset();
			runMainMenu();
		}

		game.Init(players);
	}

	private static void initGameFromXml() throws ParserConfigurationException, SAXException,
			Exception
	{
		System.out.println("Please enter the path for the xml file to load.");
		String path = reader.readLine().trim();
		game.Init(BattleShipGameLoader.load(path));
	}

	private static PlayerBase runCreateNewPlayerMenu() throws IOException
	{
		boolean keepRunning = true;

		String input = "";
		String playerName = "";

		PlayerBase player = null;
		BattleShipsGrid grid = null;

		while (keepRunning)
		{
			printCreateNewPlayerMenu();
			input = reader.readLine().trim().toUpperCase();

			if (input.equals(START_MANUAL_GAME))
			{
				playerName = runCreateHumanPlayer();

				if (playerName.equals(BACK))
				{
					return null;
				}
				else
				{
					player = new HumanPlayer(playerName);
					grid = runShipsSettingMenu();
					player.setOwnGrid(grid);

					keepRunning = false;
				}
			}
			else if (input.equals(START_PRE_SET_GAME))
			{
				player = new ComputerPlayer();

				System.out.println(String.format("PC player \"%1s\" created successfuly.\n",
						player.getName()));

				keepRunning = false;
			}
			else if (input.equals(BACK))
			{
				return null;
			}
			else
			{
				System.out.println("The entered input is illegal. Valid input is 1-2|B\n");
			}
		}

		return player;
	}

	private static void printCreateNewPlayerMenu()
	{
		System.out.println("Please enter your choice:");
		System.out.println("1. Create human player.");
		System.out.println("2. Create CPU player.");
		System.out.println("B. Back to main menu.");
	}

	private static int runSetNumOfPlayersMenu() throws IOException
	{
		boolean keepRunning = true;
		String input = "";
		int numOfPlayers = 2;

		while (keepRunning)
		{
			printSetNumOfPlayers();
			input = reader.readLine().trim().toUpperCase();
			numOfPlayers = (int) (input.charAt(0) - ZERO_DIGIT_CHAR);

			if (numOfPlayers < BattleShipsGame.MIN_NUM_OF_PLAYERS
					|| numOfPlayers > BattleShipsGame.MAX_NUM_OF_PLAYERS)
			{
				System.out.println(String.format("Number of players must be between %1d and %2d.",
						BattleShipsGame.MIN_NUM_OF_PLAYERS, BattleShipsGame.MAX_NUM_OF_PLAYERS));
			}
			else
			{
				keepRunning = false;
			}
		}

		return numOfPlayers;
	}

	private static void printSetNumOfPlayers()
	{

		System.out.println("Please enter the total number of players in the game(2-6): ");
	}

	private static String runCreateHumanPlayer() throws IOException
	{
		boolean keepRunning = true;
		String input = "";

		while (keepRunning)
		{
			printCreateHumanPlayer();
			input = reader.readLine().trim().toUpperCase();

			if (input.equals(BACK))
			{
				keepRunning = false;
			}
			else if (input.isEmpty() || input == null || input.length() < 2)
			{
				System.out
						.println("Entered name is invalid, please enter a name with length of at least 2");
			}
			else
			{
				keepRunning = false;
			}
		}

		return input;
	}

	private static void printCreateHumanPlayer()
	{
		System.out.println("Please enter human player's name or B to return to main menu:");
	}

	private static BattleShipsGrid runShipsSettingMenu() throws IOException
	{
		BattleShipsGrid grid = new BattleShipsGrid();
		
		printShipsSettingPrefix();		

		for (int i = 0; i < BattleShipsGrid.MAX_ALLOWED_SHIPS_SIZE_TWO; ++i)
		{
			runSetShip(BattleShipsGrid.TWO, grid);
		}

		for (int i = 0; i < BattleShipsGrid.MAX_ALLOWED_SHIPS_SIZE_THREE; ++i)
		{
			runSetShip(BattleShipsGrid.THREE, grid);
		}

		for (int i = 0; i < BattleShipsGrid.MAX_ALLOWED_SHIPS_SIZE_FOUR; ++i)
		{
			runSetShip(BattleShipsGrid.FOUR, grid);
		}

		for (int i = 0; i < BattleShipsGrid.MAX_ALLOWED_SHIPS_SIZE_FIVE; ++i)
		{
			runSetShip(BattleShipsGrid.FIVE, grid);
		}

		return grid;		
	}

	private static void runSetShip(int length, BattleShipsGrid grid) throws IOException
	{
		boolean keepRunning = true;
		String input = "";

		while (keepRunning)
		{
			System.out.println(String.format(
					"Please enter top left position and direction of ship of size %1d", length));
			input = reader.readLine().trim().toUpperCase();

			Point position = new Point();
			ShipDirection[] direction = new ShipDirection[1];
			direction[0] = ShipDirection.NONE;

			// keepRunning = !tryParsePositionning(input, position, direction);
			tryParsePositionning(input, position, direction);

			if (!grid.trySetShip(new BattleShip(length, position, direction[0])))
			{
				System.out.println("The input is invalid.");
			}
			else
			{
				System.out.println(grid);
				keepRunning = false;
			}
		}
	}

	private static boolean tryParsePositionning(String input, Point position, ShipDirection[] direction)
	{
		String[] splitted = input.split(":");

		if (splitted.length == 2)
		{
			try
			{
				position.setLocation(BattleShipGameLoader.mapBoatPositionToPoint(splitted[0]));
				direction[0] = BattleShipGameLoader.mapBoatDirection(splitted[1]);
			}
			catch (Exception e)
			{
				return false;
			}
			
			if (position == null || direction[0] == ShipDirection.NONE)
			{
				return false;
			}
			else 
			{
				return true;
			}
		}
		else 
		{
			return false;
		}
		
	}

	private static void printShipsSettingPrefix()
	{
		System.out.println("Now you need to position your ships on the grid.");
		System.out.println("length,position,direction;length,position,direction...\n");
		System.out.println("You need to position 1 ship of length 2, two ships of length3, \n"
				+ "    1 ship of length 4 and 1 ship of length 5");
		System.out
				.println("The grid is 10X10. Its columns range from A-J and its rows from 1-10 (both inclusive).");
		System.out.println("An empty grid looks like this:");
		System.out.println(new BattleShipsGrid().toString());
		System.out.println("Direction should be entered as V for Vetical or H for Horizontal.\n");
		System.out.println("An example of a valid input: \"A1:V\"");
		System.out
				.println("No overlapping of ships is allowed nor positionning out of grid's boundaries.");
		System.out.println("You'll need to position one ship at a time.");
	}
}
